/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/


/**
 * @brief Implement a parse-tree dumper based on tree_listener and tree_walk().
 *
 * @author  WXQ#XDU
 * @date    2024.11
 */


#include "../include/amcc_log.h"
#include "../include/tree.h"
#include "../include/amcc_error.h"
#include "../include/amcc_time.h"

//  XML element's name

#define TREEROOT_TEXT   "PARSE_TREE"

// Class tree-dumper
typedef struct tree_dumper {
    t_tree_listener base ;  // instance of base class

// my private member
    int  max_depth;
    int  nodes_count;
    FILE * fp;
} t_tree_dumper;

static void dump_fileHead(FILE* fp);
static void dump_fileTail(FILE* fp, t_tree_dumper * pDumper);
static void dump_node_main(t_tree_listener_ptr pThis, t_tree_ptr node);
static void dump_node_tail(t_tree_listener_ptr pThis, t_tree_ptr node);


static void dumper_init(t_tree_dumper * dumperPtr){
    t_tree_listener * listenerPtr = (t_tree_listener_ptr)dumperPtr;

// Step 1:  initialize the basic object, two equivalent forms:
//  tree_listener_init0( listenerPtr, sizeof(t_tree_dumper) );
    tree_listener_init0(&(dumperPtr->base), sizeof(t_tree_dumper) );

// Step 2:  initialize my members
    dumperPtr->max_depth = 0;
    dumperPtr->nodes_count = 0;

// Step 3:  overrides super methods on demand
    listenerPtr->enterEveryNode     = dump_node_main;
    listenerPtr->exitEveryNode      = dump_node_tail;
}


// Dump parse tree into logfile.
void tree_dump(t_tree_ptr  root) {
    FILE * fp = fopen(amcc_get_treeFilePath(), "wt");
    if (NULL == fp) {
        ERROR_REPORT_FILEOP(EC_WRITEFILE, amcc_get_treeFilePath());
        return;
    }

    log_info( "====> DUMPing PARSE-TREE into file [%s]", amcc_get_treeFilePath() );

    dump_fileHead(fp);

    t_tree_dumper  dumper;
    if (NULL == root) {
        dumper.max_depth = 0;
        dumper.nodes_count = 0;
    } else {
        dumper_init(&dumper);
        dumper.fp = fp;
        tree_walk(root, (t_tree_listener_ptr)&dumper);
    }

    dump_fileTail(fp, &dumper);
    fclose(fp);
    log_info( "====> DUMPed  PARSE-TREE into file [%s],"
              " total %d nodes, max depth is %d",
              amcc_get_treeFilePath(),
              dumper.nodes_count, dumper.max_depth);
}


extern const char * cstring_toXMLstring(char * buff, const char * src);  // util/stringc.c

static void dump_fileHead(FILE* fp) {
    fprintf(fp, "<?xml version=\"1.0\"  encoding=\"GBK\"?>\n");
    fprintf(fp, "<?xml-model href=\"" XML_XSD_TREE "\" ?>\n");
    const char * nowTimeTxt = amcc_get_timestring(NULL);
    char buff[FILENAME_MAX];
    fprintf(fp, "<" TREEROOT_TEXT
            " xmlns=\"" XML_NAMESPACE_TREE "\""
            " generator=\"" AMCC_PRJ_NAME "\"  infile=\"%s\""
            " ctime=\"%s\" >\n",
            cstring_toXMLstring(buff, amcc_get_inputFilePath()),
            nowTimeTxt);
}

static void dump_fileTail(FILE* fp, t_tree_dumper * pDumper) {
    fprintf(fp,  "</" TREEROOT_TEXT ">\n"
                 "<!-- total %d nodes, max depth is %d -->\n",
            pDumper->nodes_count,   pDumper->max_depth);
}

static void dump_node_header(t_tree_listener_ptr pThis, t_tree_ptr node) {
    t_tree_dumper * pDumper = (t_tree_dumper*)pThis;
    pDumper->nodes_count += 1;
    if (pThis->depthCounter > pDumper->max_depth)
        pDumper->max_depth = pThis->depthCounter;
}


static void dump_node_tail(t_tree_listener_ptr pThis, t_tree_ptr node) {
    t_tree_dumper * pDumper = (t_tree_dumper*)pThis;
    bool isNonTerminal = tree_is_nonTerminal(node);
    if ( isNonTerminal ) {
        fprintf(pDumper->fp,
                "%.*s</%s> <!-- id=\"%tx\" -->\n",
                2 * (pThis->depthCounter - 1), amcc_long_spacestring(),
                tree_get_nonTerminal(node)->ntName,
                (ptrdiff_t)node);
    }
}


#include "../include/typesys.h"
#include "../include/symentry.h"

static const char * getTypeName_(t_tree_ptr node, t_symentry_ptr symentryPtr) {
    if (NULL != symentryPtr) {
        t_type_ptr typePtr = symentry_type(symentryPtr);
        return  type_name(typePtr);
    }

    t_type_ptr  typePtr = EXPRTYPE_GET(node);
    if (NULL != typePtr)
        return type_name(typePtr);

    return "0";
}


static void dump_node_attribute(t_tree_listener_ptr pThis, t_tree_ptr node) {
    t_tree_dumper * pDumper = (t_tree_dumper*)pThis;
    bool isNonTerminal = tree_is_nonTerminal(node);
    bool isTerminal = tree_is_terminal(node);
    t_position pos1 = tree_get_startPosition(node);
    t_position pos2 = tree_get_stopPosition(node);
    const char * nodeHeadText ;
    if (isNonTerminal)
        nodeHeadText = tree_get_nonTerminal(node)->ntName;
    else if (isTerminal)
        nodeHeadText = token_get_kindName(tree_get_token(node));
    else
        nodeHeadText = EPSILON_TEXT ;

    t_symentry_ptr symentryPtr = tree_get_symbolEntry(node);
    const char * typeName = getTypeName_(node, symentryPtr);

    fprintf(pDumper->fp, "%.*s<%s",
             2 * (pThis->depthCounter - 1), amcc_long_spacestring(),
            nodeHeadText);
    const char * fmt1 = " id=\"%tx\"  parent=\"%tx\""
                        "  childs=\"%d\"  prodId=\"%d\"";
    const char * fmt2 = "  lexeme=\"%s\"";
    const char * fmt3 = "  type=\"%s\"";
    const char * fmt4 = "  symEntry=\"%tx\""
                        "  depth=\"%d\" pos1=\"%d:%d\"  pos2=\"%d:%d\""
            ;
    char  buff[FILENAME_MAX];

    fprintf( pDumper->fp, fmt1,
             (ptrdiff_t)node,       (ptrdiff_t)(node->parent),
             tree_get_childCount(node),
             node->marker.productionId);
    if (isTerminal)
        fprintf( pDumper->fp, fmt2,
                 cstring_toXMLstring(buff, token_get_text(tree_get_token(node))));
    fprintf( pDumper->fp, fmt3,
             cstring_toXMLstring(buff, typeName) );
    fprintf( pDumper->fp, fmt4,     symentryPtr,
             pThis->depthCounter,   pos1.line,   pos1.column,
             pos2.line,             pos2.column  );

    fprintf( pDumper->fp, "%s\n",
             ( isNonTerminal ? " >" : " />") );

} // end of function dump_node_attribute()


static void dump_node_main(t_tree_listener_ptr pThis, t_tree_ptr node) {
    dump_node_header(pThis, node);
    dump_node_attribute(pThis, node);
}
